using System;
using Unity.Mathematics;
namespace EarthUtility
{

    public class Mathd
    {
        /** @brief 0.1 */
        public const double EPSILON1 = 1e-1;

        /** @brief 0.01 */
        public const double EPSILON2 = 1e-2;

        /** @brief 0.001 */
        public const double EPSILON3 = 1e-3;

        /** @brief 0.0001 */
        public const double EPSILON4 = 1e-4;

        /** @brief 0.00001 */
        public const double EPSILON5 = 1e-5;

        /** @brief 0.000001 */
        public const double EPSILON6 = 1e-6;

        /** @brief 0.0000001 */
        public const double EPSILON7 = 1e-7;

        /** @brief 0.00000001 */
        public const double EPSILON8 = 1e-8;

        /** @brief 0.000000001 */
        public const double EPSILON9 = 1e-9;

        /** @brief 0.0000000001 */
        public const double EPSILON10 = 1e-10;

        /** @brief 0.00000000001 */
        public const double EPSILON11 = 1e-11;

        /** @brief 0.000000000001 */
        public const double EPSILON12 = 1e-12;

        /** @brief 0.0000000000001 */
        public const double EPSILON13 = 1e-13;

        /** @brief 0.00000000000001 */
        public const double EPSILON14 = 1e-14;

        /** @brief 0.000000000000001 */
        public const double EPSILON15 = 1e-15;

        /** @brief 0.0000000000000001 */
        public const double EPSILON16 = 1e-16;

        /** @brief 0.00000000000000001 */
        public const double EPSILON17 = 1e-17;

        /** @brief 0.000000000000000001 */
        public const double EPSILON18 = 1e-18;

        /** @brief 0.0000000000000000001 */
        public const double EPSILON19 = 1e-19;

        /** @brief 0.00000000000000000001 */
        public const double EPSILON20 = 1e-20;

        /** @brief 0.000000000000000000001 */
        public const double EPSILON21 = 1e-21;

        /**
         * @brief pi
         */
        public const double ONE_PI = 3.14159265358979323846;

        /**
         * @brief two times pi
         */
        public const double TWO_PI = ONE_PI * 2.0;

        /**
         * @brief pi divded by two
         */
        public const double PI_OVER_TWO = ONE_PI / 2.0;

        /**
         * @brief Produces an angle in the range -Pi <= angle <= Pi which is
         * equivalent to the provided angle.
         *
         * @param angle The angle in radians.
         * @returns The angle in the range [`-Math::ONE_PI`, `Math::ONE_PI`].
         */
        public static double negativePiToPi(double angle)
        {
            return zeroToTwoPi(angle + ONE_PI) - ONE_PI;
        }

        /**
         * @brief Produces an angle in the range 0 <= angle <= 2Pi which is equivalent
         * to the provided angle.
         *
         * @param angle The angle in radians.
         * @returns The angle in the range [0, `Math::TWO_PI`].
         */
        public static double zeroToTwoPi(double angle)
        {
            double modv = mod(angle, TWO_PI);
            if (math.abs(modv) < EPSILON14 && math.abs(angle) > EPSILON14)
            {
                return TWO_PI;
            }
            return modv;
        }
        /**
         * @brief The modulo operation that also works for negative dividends.
         *
         * @param m The dividend.
         * @param n The divisor.
         * @returns The remainder.
         */
        public static double mod(double m, double n)
        {
            return math.fmod(math.fmod(m, n) + n, n);
        }

        /**
         * @brief Converts degrees to radians.
         *
         * @param angleDegrees The angle to convert in degrees.
         * @returns The corresponding angle in radians.
         */
        public static double degreesToRadians(double angleDegrees)
        {
            return angleDegrees * ONE_PI / 180.0;
        }

        /**
         * @brief Converts radians to degrees.
         *
         * @param angleRadians The angle to convert in radians.
         * @returns The corresponding angle in degrees.
         */
        public static double radiansToDegrees(double angleRadians)
        {
            return angleRadians * 180.0 / ONE_PI;
        }
        public static bool equalsEpsilon(double3 left, double3 right, double relativeEpsilon)
        {
            double3 diff = math.abs(left - right);
            return diff.x <= relativeEpsilon && diff.y <= relativeEpsilon && diff.z <= relativeEpsilon;
            //   throw new NotImplementedException();
        }
        public static bool equalsEpsilon(double left, double right, double relativeEpsilon)
        {
            return equalsEpsilon(left, right, relativeEpsilon, relativeEpsilon);
        }

        /**
         * @brief Determines if two values are equal using an absolute or relative
         * tolerance test.
         *
         * This is useful to avoid problems due to roundoff error when comparing
         * floating-point values directly. The values are first compared using an
         * absolute tolerance test. If that fails, a relative tolerance test is
         * performed. Use this test if you are unsure of the magnitudes of left and
         * right.
         *
         * @param left The first value to compare.
         * @param right The other value to compare.
         * @param relativeEpsilon The maximum inclusive delta between `left` and
         * `right` for the relative tolerance test.
         * @param absoluteEpsilon The maximum inclusive delta between `left` and
         * `right` for the absolute tolerance test.
         * @returns `true` if the values are equal within the epsilon; otherwise,
         * `false`.
         *
         * @snippet TestMath.cpp equalsEpsilon
         */
        public static bool equalsEpsilon(
            double left,
            double right,
            double relativeEpsilon,
            double absoluteEpsilon)
        {
            double diff = math.abs(left - right);
            return diff <= absoluteEpsilon || diff <= relativeEpsilonToAbsolute(left, right, relativeEpsilon);
        }

        /**
           * @brief Converts a relative to an absolute epsilon, for the epsilon-equality
           * check between two values.
           *
           * @param a The first value.
           * @param b The the second value.
           * @param relativeEpsilon The relative epsilon.
           * @return The absolute epsilon.
           */
        public static double relativeEpsilonToAbsolute(
            double a,
            double b,
            double relativeEpsilon)
        {
            return relativeEpsilon * math.max(math.abs(a), math.abs(b));
        }

        public static double roundDown(double value, double tolerance)
        {
            double up = math.ceil(value);
            double down = math.floor(value);
            if (up - value < tolerance)
            {
                return up;
            }
            else
            {
                return down;
            }
        }

        public static double roundUp(double value, double tolerance)
        {
            double up = math.ceil(value);
            double down = math.floor(value);
            if (value - down < tolerance)
            {
                return down;
            }
            else
            {
                return up;
            }
        }

        /**
* Converts a longitude value, in radians, to the range [`-Math::ONE_PI`,
* `Math::ONE_PI`).
*
* @param angle The longitude value, in radians, to convert to the range
* [`-Math::ONE_PI`, `Math::ONE_PI`).
* @returns The equivalent longitude value in the range [`-Math::ONE_PI`,
* `Math::ONE_PI`).
*
* @snippet TestMath.cpp convertLongitudeRange
*/
        public static double convertLongitudeRange(double angle)
        {
            double twoPi = TWO_PI;

            double simplified = angle - math.floor(angle / twoPi) * twoPi;

            if (simplified < -ONE_PI)
            {
                return simplified + twoPi;
            }
            if (simplified >= ONE_PI)
            {
                return simplified - twoPi;
            }
            return simplified;
        }

        internal static double fromSNorm(double value, double rangeMaximum = 255.0)
        {
            return (math.clamp(value, 0.0, rangeMaximum) / rangeMaximum) * 2.0 - 1.0;
        }

        internal static double signNotZero(double value)
        {
            return value < 0.0 ? -1.0 : 1.0;
        }
    }
}